<?php

    class Cicp_Vector3d
{
    private $_values = array(0, 0, 0);
    private $_aliases = array();

    function __toString()
    {
        $vals = array();
        foreach ($this->_values as $v) {
            $vals[] = '' . round($v, 3);
        }
        return implode(',', $vals);
    }

    function __set($variable, $value)
    {
        $this->setValue($this->_aliases[$variable], $value);
    }

    function __get($variable)
    {
        return $this->_values[$this->_aliases[$variable]];
    }

    function setValue($index, $value)
    {
        if (array_key_exists($index, $this->_values)) {
            $this->_values[$index] = $value;
        }
    }

    function getValue($index)
    {
        if (array_key_exists($index, $this->_values)) {
            return $this->_values[$index];
        }
    }

    function setIndexAlias($alias, $index)
    {
        $this->_aliases[$alias] = $index;
    }

    function setValues($v1, $v2, $v3)
    {
        $this->setValue(0, $v1);
        $this->setValue(1, $v2);
        $this->setValue(2, $v3);
        return $this;
    }

    function getValues()
    {
        return $this->_values;
    }

    function abs()
    {
        $componentSum = 0;
        foreach ($this->_values as $v) {
            $componentSum += pow($v, 2);
        }
        return sqrt($componentSum);
    }

    function length()
    {
        return $this->abs();
    }

    function normalize()
    {
        return $this->divide($this->length());
    }

    function substract($summand)
    {
        if ($summand instanceof Cicp_Vector3d) {
            $invertedVector = $summand->multiply(-1);
            $summand = $invertedVector;
        }
        else {
            $summand = -$summand;
        }
        return $this->add($summand);
    }

    function setLength($length)
    {
        $normalized = $this->normalize();
        $stretched = $normalized->multiply($length);
        return $stretched;
    }

    function add($summand)
    {
        $resultVector = new Cicp_Vector3d;
        if ($summand instanceof Cicp_Vector3d) {
            $summandValues = $summand->getValues();
            foreach ($this->_values as $index => $v) {
                $resultVector->setValue($index, $v + $summandValues[$index]);
            }
        }
        else {
            foreach ($this->_values as $index => $v) {
                $resultVector->setValue($index, $v + $summand);
            }
        }
        return $this->transportAliases($resultVector);
    }

    function divide($divisor)
    {
        $resultVector = new Cicp_Vector3d;
        $i = 0;
        foreach ($this->_values as $index => $v) {
            $resultVector->setValue($i++, $v / $divisor);
        }
        return $this->transportAliases($resultVector);
    }

    function multiply($factor)
    {
        if ($factor instanceof Cicp_Vector3d) {
            $result = 0;
            foreach ($this->_values as $index => $v) {
                $result += $factor->getValue($index) * $v;
            }
            return $result;
        }
        else {
            $resultVector = new Cicp_Vector3d;
            foreach ($this->_values as $index => $v) {
                $resultVector->setValue($index, $v * $factor);
            }
            return $this->transportAliases($resultVector);
        }
    }

    function transportAliases($v2)
    {
        foreach ($this->_aliases as $alias => $val) {
            $v2->setIndexAlias($alias, $val);
        }
        return $v2;
    }

    function rotateByAxis($axisNum, $degrees) 
    {
        $rV = clone $this;
        $coordinates =  $rV->getValues();
        switch ($axisNum) {
        case 0:
            $coordinates[1] = $rV->getValue(1) * cos($degrees)
                - $rV->getValue(2) * sin($degrees);
            $coordinates[2] = $rV->getValue(1) * sin($degrees)
                + $rV->getValue(2) * cos($degrees);
            break;
        case 1:
            $coordinates[0] = $rV->getValue(2) * sin($degrees)
                - $rV->getValue(0) * cos($degrees);
            $coordinates[2] = $rV->getValue(2) * cos($degrees)
                - $rV->getValue(0) * sin($degrees);
            break;
        case 2: // if axisnum is not valid, third axis is taken
        default:
            $coordinates[0] = $rV->getValue(0) * cos($degrees)
                - $rV->getValue(1) * sin($degrees);
            $coordinates[1] = $rV->getValue(0) * sin($degrees)
                - $rV->getValue(1) * cos($degrees);
            break;
        }
        // write to result vector
        $rV->setValues($coordinates[0], $coordinates[1], $coordinates[2]);
        return $this->transportAliases($rV);
    }
}